grammar Javascript;

options {
	output = AST ;
}

tokens {
	NULL		= 'null' ;
	TRUE		= 'true' ;
	FALSE		= 'false' ;

	BREAK		= 'break' ;
	CASE		= 'case' ;
	CATCH 		= 'catch' ;
	CONTINUE 	= 'continue' ;
	DEFAULT		= 'default' ;
	DELETE		= 'delete' ;
	DO 		    = 'do' ;
	ELSE 		= 'else' ;
	FINALLY 	= 'finally' ;
	FOR 		= 'for' ;
	FUNCTION 	= 'function' ;
	IF 		    = 'if' ;
	IN 		    = 'in' ;
	INSTANCEOF 	= 'instanceof' ;
	NEW 		= 'new' ;
	RETURN 		= 'return' ;
	SWITCH 		= 'switch' ;
	THIS 		= 'this' ;
	THROW 		= 'throw' ;
	TRY 		= 'try' ;
	TYPEOF 		= 'typeof' ;
	VAR 		= 'var' ;
	VOID 		= 'void' ;
	WHILE 		= 'while' ;
	WITH 		= 'with' ;

	LBRACE		= '{' ;
	RBRACE		= '}' ;
	LPAREN		= '(' ;
	RPAREN		= ')' ;
	LBRACK		= '[' ;
	RBRACK		= ']' ;
	DOT		    = '.' ;
	SEMIC		= ';' ;
	COMMA		= ',' ;
	LT	   	    = '<' ;
	GT		    = '>' ;
	LTE		    = '<=' ;
	GTE		    = '>=' ;
	EQ		    = '==' ;
	NEQ		    = '!=' ;
	SAME		= '===' ;
	NSAME		= '!==' ;
	ADD		    = '+' ;
	SUB		= '-' ;
	MUL		= '*' ;
	MOD		= '%' ;
	INC		= '++' ;
	DEC		= '--' ;
	SHL		= '<<' ;
	SHR		= '>>' ;
	SHU		= '>>>' ;
	AND		= '&' ;
	OR		= '|' ;
	XOR		= '^' ;
	NOT		= '!' ;
	INV		= '~' ;
	LAND		= '&&' ;
	LOR		= '||' ;
	QUE		= '?' ;
	COLON		= ':' ;
	ASSIGN		= '=' ;
	ADDASS		= '+=' ;
	SUBASS		= '-=' ;
	MULASS		= '*=' ;
	MODASS		= '%=' ;
	SHLASS		= '<<=' ;
	SHRASS		= '>>=' ;
	SHUASS		= '>>>=' ;
	ANDASS		= '&=' ;
	ORASS		= '|=' ;
	XORASS		= '^=' ;
	DIV		= '/' ;
	DIVASS		= '/=' ;

    // Imaginary
	ARRAY ;
	BYFIELD ;
	BYINDEX ;
	CALL ;
	NEG ;
	OBJECT ;
	PAREXPR ;
	PDEC ;
	PINC ;
	POS ;
}

@lexer::members
{
private Token last;

private final boolean areRegularExpressionsEnabled()
{
	if (last == null)
	{
		return true;
	}
	switch (last.getType())
	{
		case Identifier:
		case NULL:
		case TRUE:
		case FALSE:
		case THIS:
		case OctalIntegerLiteral:
		case DecimalLiteral:
		case HexIntegerLiteral:
		case StringLiteral:
		case RBRACK:
		case RPAREN:
			return false;
		default:
			return true;
	}
}

private final void consumeIdentifierUnicodeStart() throws RecognitionException, NoViableAltException
{
	int ch = input.LA(1);
	if (isIdentifierStartUnicode(ch))
	{
		matchAny();
		do
		{
			ch = input.LA(1);
			if (ch == '$' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || ch == '\\' || ch == '_' || (ch >= 'a' && ch <= 'z') || isIdentifierPartUnicode(ch))
			{
				mIdentifierPart();
			}
			else
			{
				return;
			}
		}
		while (true);
	}
	else
	{
		throw new NoViableAltException();
	}
}

private final boolean isIdentifierPartUnicode(int ch)
{
	return Character.isJavaIdentifierPart(ch);
}

private final boolean isIdentifierStartUnicode(int ch)
{
	return Character.isJavaIdentifierStart(ch);
}

public Token nextToken()
{
	Token result = super.nextToken();
	if (result.getChannel() == Token.DEFAULT_CHANNEL)
	{
		last = result;
	}
	return result;
}
}

@parser::members
{

public String fileName;

private String lastedFoundFunctionName = "";

private ArrayList<FoundFunction> foundFunctions = new ArrayList<FoundFunction>();

public List<FoundFunction> getFunctions() {
	return foundFunctions;
};

public void addFunctionInfo(List<Factor> functionInfo, Factor factor) {
    if (functionInfo != null) {
        functionInfo.add(factor);
    }
}


private void recordFunction(String name, List<Factor> factors, int startLine, int endLine) {
	FoundFunction f = new FoundFunction(name, factors, startLine, endLine, fileName);
	foundFunctions.add(f);
	System.out.println(f);
}

private final boolean isLeftHandSideAssign(RuleReturnScope lhs, Object[] cached)
{

	if (cached[0] != null)
	{
		return ((Boolean)cached[0]).booleanValue();
	}

	boolean result;
	if (isLeftHandSideExpression(lhs))
	{
		switch (input.LA(1))
		{
			case ASSIGN:
			case MULASS:
			case DIVASS:
			case MODASS:
			case ADDASS:
			case SUBASS:
			case SHLASS:
			case SHRASS:
			case SHUASS:
			case ANDASS:
			case XORASS:
			case ORASS:
				result = true;
				break;
			default:
				result = false;
				break;
		}
	}
	else
	{
		result = false;
	}

	cached[0] = new Boolean(result);
	return result;
}

private final static boolean isLeftHandSideExpression(RuleReturnScope lhs)
{
	if (lhs.getTree() == null) // e.g. during backtracking
	{
		return true;
	}
	else
	{
		switch (((Tree)lhs.getTree()).getType())
		{
			case THIS:
			case Identifier:
			case NULL:
			case TRUE:
			case FALSE:
			case DecimalLiteral:
			case OctalIntegerLiteral:
			case HexIntegerLiteral:
			case StringLiteral:
			case RegularExpressionLiteral:
			case ARRAY:
			case OBJECT:
			case PAREXPR:
			case FUNCTION:
			case NEW:
			case CALL:
			case BYFIELD:
			case BYINDEX:
				return true;
			default:
				return false;
		}
	}
}

private final boolean isLeftHandSideIn(RuleReturnScope lhs, Object[] cached)
{
	if (cached[0] != null)
	{
		return ((Boolean)cached[0]).booleanValue();
	}

	boolean result = isLeftHandSideExpression(lhs) && (input.LA(1) == IN);
	cached[0] = new Boolean(result);
	return result;
}

private final void promoteEOL(ParserRuleReturnScope rule)
{
	// Get current token and its type (the possibly offending token).
	Token lt = input.LT(1);
	int la = lt.getType();

	// We only need to promote an EOL when the current token is offending (not a SEMIC, EOF, RBRACE, EOL or MultiLineComment).
	// EOL and MultiLineComment are not offending as they're already promoted in a previous call to this method.
	// Promoting an EOL means switching it from off channel to on channel.
	// A MultiLineComment gets promoted when it contains an EOL.
	if (!(la == SEMIC || la == EOF || la == RBRACE || la == EOL || la == MultiLineComment))
	{
		// Start on the possition before the current token and scan backwards off channel tokens until the previous on channel token.
		for (int ix = lt.getTokenIndex() - 1; ix > 0; ix--)
		{
			lt = input.get(ix);
			if (lt.getChannel() == Token.DEFAULT_CHANNEL)
			{
				// On channel token found: stop scanning.
				break;
			}
			else if (lt.getType() == EOL || (lt.getType() == MultiLineComment && lt.getText().matches("/.*\r\n|\r|\n")))
			{
				// We found our EOL: promote the token to on channel, position the input on it and reset the rule start.
				lt.setChannel(Token.DEFAULT_CHANNEL);
				input.seek(lt.getTokenIndex());
				if (rule != null)
				{
					rule.start = lt;
				}
				break;
			}
		}
	}
}
}

// Added for lexing purposes

fragment BSLASH
	: '\\'
	;

fragment DQUOTE
	: '"'
	;

fragment SQUOTE
	: '\''
	;

fragment TAB
	: '\u0009'
	;

fragment VT // Vertical TAB
	: '\u000b'
	;

fragment FF // Form Feed
	: '\u000c'
	;

fragment SP // Space
	: '\u0020'
	;

fragment NBSP // Non-Breaking Space
	: '\u00a0'
	;

fragment USP // Unicode Space Separator (rest of Unicode category Zs)
	: '\u1680'  // OGHAM SPACE MARK
	| '\u180E'  // MONGOLIAN VOWEL SEPARATOR
	| '\u2000'  // EN QUAD
	| '\u2001'  // EM QUAD
	| '\u2002'  // EN SPACE
	| '\u2003'  // EM SPACE
	| '\u2004'  // THREE-PER-EM SPACE
	| '\u2005'  // FOUR-PER-EM SPACE
	| '\u2006'  // SIX-PER-EM SPACE
	| '\u2007'  // FIGURE SPACE
	| '\u2008'  // PUNCTUATION SPACE
	| '\u2009'  // THIN SPACE
	| '\u200A'  // HAIR SPACE
	| '\u202F'  // NARROW NO-BREAK SPACE
	| '\u205F'  // MEDIUM MATHEMATICAL SPACE
	| '\u3000'  // IDEOGRAPHIC SPACE
	;

WhiteSpace
	: ( TAB | VT | FF | SP | NBSP | USP )+ { $channel = HIDDEN; }
	;


fragment LF // Line Feed
	: '\n'
	;

fragment CR // Carriage Return
	: '\r'
	;

fragment LS // Line Separator
	: '\u2028'
	;

fragment PS // Paragraph Separator
	: '\u2029'
	;

fragment LineTerminator
	: CR | LF | LS | PS
	;

EOL
	: ( ( CR LF? ) | LF | LS | PS ) { $channel = HIDDEN; }
	;

MultiLineComment
	: '/*' ( options { greedy = false; } : . )* '*/' { $channel = HIDDEN; }
	;

SingleLineComment
	: '//' ( ~( LineTerminator ) )* { $channel = HIDDEN; }
	;

token
	: reservedWord
	| Identifier
	| punctuator
	| numericLiteral
	| StringLiteral
	;

reservedWord
	: keyword
	| NULL
	| booleanLiteral
	;

keyword
	: BREAK
	| CASE
	| CATCH
	| CONTINUE
	| DEFAULT
	| DELETE
	| DO
	| ELSE
	| FINALLY
	| FOR
	| FUNCTION
	| IF
	| IN
	| INSTANCEOF
	| NEW
	| RETURN
	| SWITCH
	| THIS
	| THROW
	| TRY
	| TYPEOF
	| VAR
	| VOID
	| WHILE
	| WITH
	;

fragment IdentifierStartASCII
	: 'a'..'z' | 'A'..'Z'
	| '$'
	| '_'
	| BSLASH 'u' HexDigit HexDigit HexDigit HexDigit // UnicodeEscapeSequence
	;

fragment IdentifierPart
	: DecimalDigit
	| IdentifierStartASCII
	| { isIdentifierPartUnicode(input.LA(1)) }? { matchAny(); }
	;

fragment IdentifierNameASCIIStart
	: IdentifierStartASCII IdentifierPart*
	;

Identifier
	: IdentifierNameASCIIStart
	| { consumeIdentifierUnicodeStart(); }
	;

punctuator
	: LBRACE
	| RBRACE
	| LPAREN
	| RPAREN
	| LBRACK
	| RBRACK
	| DOT
	| SEMIC
	| COMMA
	| LT
	| GT
	| LTE
	| GTE
	| EQ
	| NEQ
	| SAME
	| NSAME
	| ADD
	| SUB
	| MUL
	| MOD
	| INC
	| DEC
	| SHL
	| SHR
	| SHU
	| AND
	| OR
	| XOR
	| NOT
	| INV
	| LAND
	| LOR
	| QUE
	| COLON
	| ASSIGN
	| ADDASS
	| SUBASS
	| MULASS
	| MODASS
	| SHLASS
	| SHRASS
	| SHUASS
	| ANDASS
	| ORASS
	| XORASS
	| DIV
	| DIVASS
	;

literal
	: NULL
	| booleanLiteral
	| numericLiteral
	| StringLiteral
	| RegularExpressionLiteral
	;

booleanLiteral
	: TRUE
	| FALSE
	;

fragment DecimalDigit
	: '0'..'9'
	;

fragment HexDigit
	: DecimalDigit | 'a'..'f' | 'A'..'F'
	;

fragment OctalDigit
	: '0'..'7'
	;

fragment ExponentPart
	: ( 'e' | 'E' ) ( '+' | '-' )? DecimalDigit+
	;

fragment DecimalIntegerLiteral
	: '0'
	| '1'..'9' DecimalDigit*
	;

DecimalLiteral
	: DecimalIntegerLiteral '.' DecimalDigit* ExponentPart?
	| '.' DecimalDigit+ ExponentPart?
	| DecimalIntegerLiteral ExponentPart?
	;

OctalIntegerLiteral
	: '0' OctalDigit+
	;

HexIntegerLiteral
	: ( '0x' | '0X' ) HexDigit+
	;

numericLiteral
	: DecimalLiteral
	| OctalIntegerLiteral
	| HexIntegerLiteral
	;

fragment CharacterEscapeSequence
	: ~( DecimalDigit | 'x' | 'u' | LineTerminator ) // Concatenation of SingleEscapeCharacter and NonEscapeCharacter
	;

fragment ZeroToThree
	: '0'..'3'
	;

fragment OctalEscapeSequence
	: OctalDigit
	| ZeroToThree OctalDigit
	| '4'..'7' OctalDigit
	| ZeroToThree OctalDigit OctalDigit
	;

fragment HexEscapeSequence
	: 'x' HexDigit HexDigit
	;

fragment UnicodeEscapeSequence
	: 'u' HexDigit HexDigit HexDigit HexDigit
	;

fragment EscapeSequence
	:
	BSLASH
	(
		CharacterEscapeSequence
		| OctalEscapeSequence
		| HexEscapeSequence
		| UnicodeEscapeSequence
	)
	;

StringLiteral
	: SQUOTE ( ~( SQUOTE | BSLASH | LineTerminator ) | EscapeSequence )* SQUOTE
	| DQUOTE ( ~( DQUOTE | BSLASH | LineTerminator ) | EscapeSequence )* DQUOTE
	;

fragment BackslashSequence
	: BSLASH ~( LineTerminator )
	;

fragment RegularExpressionFirstChar
	: ~ ( LineTerminator | MUL | BSLASH | DIV )
	| BackslashSequence
	;

fragment RegularExpressionChar
	: ~ ( LineTerminator | BSLASH | DIV )
	| BackslashSequence
	;

RegularExpressionLiteral
	: { areRegularExpressionsEnabled() }?=> DIV RegularExpressionFirstChar RegularExpressionChar* DIV IdentifierPart*
	;

primaryExpression
	: THIS
	| Identifier
	| literal
	| arrayLiteral
	| objectLiteral
	| LPAREN expression[null] RPAREN
	;

arrayLiteral
	: LBRACK ( arrayItem ( COMMA arrayItem )* )? RBRACK
	;

arrayItem
	: ( assignmentExpression["anonymous", null] | { input.LA(1) == COMMA }? )
	;

objectLiteral
	: LBRACE ( nameValuePair ( COMMA nameValuePair )* )? RBRACE
	;

nameValuePair
	: propertyName COLON assignmentExpression[$propertyName.functionName, null]
	;

propertyName returns [String functionName]
	: Identifier {$functionName=$Identifier.text;}
	| StringLiteral {$functionName=$StringLiteral.text;}
	| numericLiteral {$functionName="It's a number-.-";}
	;

memberExpression[String functionName]
	: primaryExpression
	| functionExpression[functionName]
	| newExpression
	;

newExpression
	: NEW primaryExpression
	;


arguments
	: LPAREN ( assignmentExpression["anonymous", null] ( COMMA assignmentExpression["anonymous", null] )* )? RPAREN
	;

leftHandSideExpression[String functionName]
	:   (memberExpression[functionName] -> memberExpression) leftHandSideExpressionTail*
	;

leftHandSideExpressionTail
    :   arguments
	|   LBRACK expression[null] RBRACK
	|   DOT Identifier {lastedFoundFunctionName = $Identifier.text;}   
    ;

postfixExpression[String functionName]
	: leftHandSideExpression[functionName] { if (input.LA(1) == INC || input.LA(1) == DEC) promoteEOL(null); } ( postfixOperator )?
	;

postfixOperator
	: op=INC { $op.setType(PINC); }
	| op=DEC { $op.setType(PDEC); }
	;

unaryExpression[String functionName]
	: postfixExpression[functionName]
	| unaryOperator unaryExpression[functionName]
	;

unaryOperator
	: DELETE
	| VOID
	| TYPEOF
	| INC
	| DEC
	| op=ADD { $op.setType(POS); }
	| op=SUB { $op.setType(NEG); }
	| INV
	| NOT
	;

multiplicativeExpression[String functionName]
	: unaryExpression[functionName] ( ( MUL | DIV | MOD ) unaryExpression[functionName] )*
	;

additiveExpression[String functionName]
	: multiplicativeExpression[functionName] ( ( ADD | SUB ) multiplicativeExpression["anonymous"] )*
	;

shiftExpression[String functionName]
	: additiveExpression[functionName] ( ( SHL | SHR | SHU ) additiveExpression["anonymous"] )*
	;

relationalExpression[String functionName]
	: shiftExpression[functionName] ( ( LT | GT | LTE | GTE | INSTANCEOF | IN ) shiftExpression["anonymous"] )*
	;

relationalExpressionNoIn
	: shiftExpression["in relationalExpressionNoIn"] ( ( LT | GT | LTE | GTE | INSTANCEOF ) shiftExpression["anonymous"] )*
	;

equalityExpression[String functionName]
	: relationalExpression[functionName] ( ( EQ | NEQ | SAME | NSAME ) relationalExpression["anonymous"] )*
	;

equalityExpressionNoIn
	: relationalExpressionNoIn ( ( EQ | NEQ | SAME | NSAME ) relationalExpressionNoIn )*
	;

bitwiseANDExpression[String functionName]
	: equalityExpression[functionName] ( AND equalityExpression["anonymous"] )*
	;

bitwiseANDExpressionNoIn
	: equalityExpressionNoIn ( AND equalityExpressionNoIn )*
	;

bitwiseXORExpression[String functionName]
	: bitwiseANDExpression[functionName] ( XOR bitwiseANDExpression["anonymous"] )*
	;

bitwiseXORExpressionNoIn
	: bitwiseANDExpressionNoIn ( XOR bitwiseANDExpressionNoIn )*
	;

bitwiseORExpression[String functionName]
	: bitwiseXORExpression[functionName] ( OR bitwiseXORExpression["anonymous"] )*
	;

bitwiseORExpressionNoIn
	: bitwiseXORExpressionNoIn ( OR bitwiseXORExpressionNoIn )*
	;

logicalANDExpression[String functionName]
	: bitwiseORExpression[functionName] ( LAND bitwiseORExpression["anonymous"] )*
	;

logicalANDExpressionNoIn
	: bitwiseORExpressionNoIn ( LAND bitwiseORExpressionNoIn )*
	;

logicalORExpression[String functionName]
	: logicalANDExpression[functionName] ( LOR logicalANDExpression["anonymous"] )*
	;

logicalORExpressionNoIn
	: logicalANDExpressionNoIn ( LOR logicalANDExpressionNoIn )*
	;

conditionalExpression[String functionName]
	: logicalORExpression[functionName] ( QUE assignmentExpression["anonymous", null] COLON assignmentExpression["anonymous", null] )?
	;

conditionalExpressionNoIn
	: logicalORExpressionNoIn ( QUE assignmentExpressionNoIn COLON assignmentExpressionNoIn )?
	;

assignmentExpression[String functionName, List<Factor> functionInfo]
@init
{
	Object[] isLhs = new Object[1];
}
	:lhs=conditionalExpression[functionName]
	( { isLeftHandSideAssign(lhs, isLhs) }?  assignmentOperator assignmentExpression[lastedFoundFunctionName, null] { addFunctionInfo(functionInfo, new Factor("assignment")); })?
	;

assignmentOperator
	: ASSIGN | MULASS | DIVASS | MODASS | ADDASS | SUBASS | SHLASS | SHRASS | SHUASS | ANDASS | XORASS | ORASS
	;

assignmentExpressionNoIn
@init
{
	Object[] isLhs = new Object[1];
}
	: lhs=conditionalExpressionNoIn
	( { isLeftHandSideAssign(lhs, isLhs) }? assignmentOperator assignmentExpressionNoIn )?
	;

expression[List<Factor> functionInfo]
	: assignmentExpression["in expression", functionInfo] ( COMMA assignmentExpression["in expression", functionInfo] )*
	;

expressionNoIn
	: assignmentExpressionNoIn ( COMMA assignmentExpressionNoIn )*
	;

semic
@init
{
	// Mark current position so we can unconsume a RBRACE.
	int marker = input.mark();
	// Promote EOL if appropriate
	promoteEOL(retval);
}
	: SEMIC
	| EOF
	| RBRACE { input.rewind(marker); }
	| EOL | MultiLineComment // (with EOL in it)
	;

statement[List<Factor> functionInfo]
options
{
	k = 1 ;
}
	: { input.LA(1) == LBRACE }? block
	| statementTail[functionInfo]
	;

statementTail[List<Factor> functionInfo]
	: variableStatement
	| emptyStatement
	| expressionStatement[functionInfo]
	| ifStatement[functionInfo]
	| iterationStatement[functionInfo]
	| continueStatement
	| breakStatement
	| returnStatement
	| withStatement
	| labelledStatement
	| switchStatement
	| throwStatement
	| tryStatement
	;

block
	: LBRACE statement[null]* RBRACE
	;

variableStatement
	: VAR variableDeclaration ( COMMA variableDeclaration )* semic
	;

variableDeclaration
	: Identifier ( ASSIGN assignmentExpression[$Identifier.text, null] )?
	;

variableDeclarationNoIn
	: Identifier ( ASSIGN assignmentExpressionNoIn )?
	;

emptyStatement
	: SEMIC
	;

expressionStatement[List<Factor> functionInfo]
	: expression[functionInfo] semic
	;

ifStatement[List<Factor> functionInfo]
@init {
    addFunctionInfo(functionInfo, new Factor("if"));
}
	: IF LPAREN expression[null] RPAREN statement[null] ( { input.LA(1) == ELSE }? ELSE statement[null] )?
	;

iterationStatement[List<Factor> functionInfo]
	: doStatement[functionInfo]
	| whileStatement[functionInfo]
	| forStatement[functionInfo]
	;

doStatement[List<Factor> functionInfo]
@init {
    addFunctionInfo(functionInfo, new Factor("do"));
    System.out.println("found a do loop");
}
	: DO statement[null] WHILE LPAREN expression[null] RPAREN semic
	;

whileStatement[List<Factor> functionInfo]
@init {
    addFunctionInfo(functionInfo, new Factor("while"));
    System.out.println("found a while loop");
}
	: WHILE LPAREN expression[null] RPAREN statement[null]
	;

forStatement[List<Factor> functionInfo]
@init {
    addFunctionInfo(functionInfo, new Factor("for"));
    System.out.println("found a for loop");
}
	: FOR LPAREN forControl RPAREN statement[null]
	;

forControl
	: forControlVar
	| forControlExpression
	| forControlSemic
	;

forControlVar
	: VAR variableDeclarationNoIn
	(
		(
			IN expression[null]
		)
		|
		(
			( COMMA variableDeclarationNoIn )* SEMIC expression[null]? SEMIC expression[null]?
		)
	)
	;

forControlExpression
@init
{
	Object[] isLhs = new Object[1];
}
	: ex1=expressionNoIn
	(
		{ isLeftHandSideIn(ex1, isLhs) }? (
			IN expression[null]
		)
		| SEMIC expression[null]? SEMIC expression[null]?
	)
	;

forControlSemic
	: SEMIC expression[null]? SEMIC expression[null]?
	;

continueStatement
	: CONTINUE { if (input.LA(1) == Identifier) promoteEOL(null); } Identifier? semic
	;

breakStatement
	: BREAK { if (input.LA(1) == Identifier) promoteEOL(null); } Identifier? semic
	;

returnStatement
	: RETURN { promoteEOL(null); } expression[null]? semic
	;

withStatement
	: WITH LPAREN expression[null] RPAREN statement[null]
	;

switchStatement
@init
{
	int defaultClauseCount = 0;
}
	: SWITCH LPAREN expression[null] RPAREN LBRACE ( { defaultClauseCount == 0 }?=> defaultClause { defaultClauseCount++; } | caseClause )* RBRACE
	;

caseClause
	: CASE expression[null] COLON statement[null]*
	;

defaultClause
	: DEFAULT COLON statement[null]*
	;

labelledStatement
	: Identifier COLON statement[null]
	;

throwStatement
	: THROW { promoteEOL(null); } expression[null] semic
	;

tryStatement
	: TRY block ( catchClause finallyClause? | finallyClause )
	;

catchClause
	: CATCH LPAREN Identifier RPAREN block
	;

finallyClause
	: FINALLY block
	;

functionDeclaration
	: FUNCTION Identifier formalParameterList functionBody[$Identifier.text]
	;

functionExpression[String functionName]
	: FUNCTION Identifier? formalParameterList functionBody[functionName]
	;

formalParameterList
	: LPAREN ( Identifier ( COMMA Identifier )* )? RPAREN
	;

functionBody[String functionName]
@init {
    List<Factor> functionInfo = new ArrayList<Factor>();
}
	:LBRACE sourceElement[functionInfo]* RBRACE {recordFunction(functionName, functionInfo, $LBRACE.getLine(), $RBRACE.getLine()); }
	;

program
	: sourceElement[null]*
	;

sourceElement[List<Factor> functionInfo]
options {
	k = 1 ;
}
	: { input.LA(1) == FUNCTION }? functionDeclaration
	| statement[functionInfo]
	;
